//
// Created by ziya on 22-9-14.
//

#include "../../include/core/elf.h"
#include "../../include/core/buffer.h"

static void parse_elf64_header(buffer_t* buffer, Elf64_Ehdr* ehdr) {
    assert(NULL != buffer, "buffer = null");
    assert(NULL != ehdr, "ehdr = null");

    INFO_PRINT("开始解析64位ELF文件头\n");

    // 前16字节
    buffer_copy(buffer, ehdr, EI_NIDENT);

    ehdr->e_type = buffer_read2(buffer);
    ehdr->e_machine = buffer_read2(buffer);
    ehdr->e_version = buffer_read4(buffer);
    ehdr->e_entry = buffer_read8(buffer);
    ehdr->e_phoff = buffer_read8(buffer);
    ehdr->e_shoff = buffer_read8(buffer);
    ehdr->e_flags = buffer_read4(buffer);
    ehdr->e_ehsize = buffer_read2(buffer);
    ehdr->e_phentsize = buffer_read2(buffer);
    ehdr->e_phnum = buffer_read2(buffer);
    ehdr->e_shentsize = buffer_read2(buffer);
    ehdr->e_shnum = buffer_read2(buffer);
    ehdr->e_shstrndx = buffer_read2(buffer);

    printf("文件头大小(e_ehsize): %d\n", ehdr->e_ehsize);

    printf("程序头表偏移(e_phoff): %d(0x%x)\n", ehdr->e_phoff, ehdr->e_phoff);
    printf("程序头表中的记录数(e_phnum): %d(0x%x)\n", ehdr->e_phnum, ehdr->e_phnum);
    printf("程序头表中每条记录大小(e_phentsize): %d(0x%x)\n", ehdr->e_phentsize, ehdr->e_phentsize);

    printf("节表偏移(e_shoff): %d(0x%x)\n", ehdr->e_shoff, ehdr->e_shoff);
    printf("节表中的记录数(e_shnum): %d(0x%x)\n", ehdr->e_shnum, ehdr->e_shnum);
    printf("节表中每条记录大小(e_shentsize): %d(0x%x)\n", ehdr->e_shentsize, ehdr->e_shentsize);

    printf("字符串表索引(e_shstrndx): %d(0x%x)\n", ehdr->e_shstrndx, ehdr->e_shstrndx);
}

static void parse_elf64_shstrtab(buffer_t* buffer, elf_t* elf) {
    assert(NULL != buffer, "buffer = null");
    assert(NULL != elf, "elf = null");

    INFO_PRINT("开始解析shstrtab\n");

    // 获取.shstrtab项的信息
    Elf64_Shdr* shdr = buffer->data + elf->e_elf.elf64->e_shoff + elf->e_elf.elf64->e_shentsize * elf->e_elf.elf64->e_shstrndx;

    elf->e_shstring_table = Calloc(shdr->sh_size, sizeof(char));
    memcpy(elf->e_shstring_table, buffer->data + shdr->sh_offset, shdr->sh_size);

    printf("完成节表名称的保存: off: %d(%x), size:%d\n", shdr->sh_offset, shdr->sh_offset, shdr->sh_size);
}

static void parse_elf64_section_table(buffer_t* buffer, elf_t* elf) {
    assert(NULL != buffer, "buffer = null");
    assert(NULL != elf, "elf = null");

    INFO_PRINT("开始解析节表\n");

    elf->e_shdr.shdr64 = Calloc(elf->e_elf.elf64->e_shnum, sizeof(Elf64_Shdr));

    buffer->off = elf->e_elf.elf64->e_shoff;

    printf("%s \t\t %s \t\t %s \t\t %s\n", "index", "name", "off", "size");

    for (int i = 0; i < elf->e_elf.elf64->e_shnum; ++i) {
        elf->e_shdr.shdr64[i].sh_name = buffer_read4(buffer);
        elf->e_shdr.shdr64[i].sh_type = buffer_read4(buffer);
        elf->e_shdr.shdr64[i].sh_flags = buffer_read8(buffer);
        elf->e_shdr.shdr64[i].sh_addr = buffer_read8(buffer);
        elf->e_shdr.shdr64[i].sh_offset = buffer_read8(buffer);
        elf->e_shdr.shdr64[i].sh_size = buffer_read8(buffer);
        elf->e_shdr.shdr64[i].sh_link = buffer_read4(buffer);
        elf->e_shdr.shdr64[i].sh_info = buffer_read4(buffer);
        elf->e_shdr.shdr64[i].sh_addralign = buffer_read8(buffer);
        elf->e_shdr.shdr64[i].sh_entsize = buffer_read8(buffer);

        printf("%d \t\t %s(%d) \t\t %x \t\t %x\n", i, elf->e_shstring_table + elf->e_shdr.shdr64[i].sh_name,
               elf->e_shdr.shdr64[i].sh_name, elf->e_shdr.shdr64[i].sh_offset, elf->e_shdr.shdr64[i].sh_size);
    }
}

static void parse_elf64_symtab(buffer_t* buffer, elf_t* elf, Elf64_Shdr* shdr) {
    assert(NULL != buffer, "buffer = null");
    assert(NULL != elf, "elf = null");
    assert(NULL != shdr, "shdr = null");

    elf->e_sym.sym64 = Calloc(shdr->sh_size, 1);

    buffer->off = shdr->sh_offset;

    for (int i = 0; i < shdr->sh_size / shdr->sh_entsize; ++i) {
        elf->e_sym.sym64[i].st_name = buffer_read4(buffer);
        elf->e_sym.sym64[i].st_info = buffer_read1(buffer);
        elf->e_sym.sym64[i].st_other = buffer_read1(buffer);
        elf->e_sym.sym64[i].st_shndx = buffer_read2(buffer);
        elf->e_sym.sym64[i].st_value = buffer_read8(buffer);
        elf->e_sym.sym64[i].st_size = buffer_read8(buffer);

        printf("%d %d\n", i, elf->e_sym.sym64[i].st_name);
    }
}

static void parse_elf64_dynamic(buffer_t* buffer, elf_t* elf, Elf64_Shdr* shdr) {
    assert(NULL != buffer, "buffer = null");
    assert(NULL != elf, "elf = null");
    assert(NULL != shdr, "shdr = null");

    elf->e_dyn.dyn64 = Calloc(shdr->sh_size, 1);

    buffer->off = shdr->sh_offset;

    for (int i = 1;; ++i) {
        elf->e_dyn.dyn64[i].d_tag = buffer_read8(buffer);

        if (0 == elf->e_dyn.dyn64[i].d_tag) {
            // 这个段的数据远少于占用的空间, 判断是否为0就知道实际数据是否解析结束
            printf("%d 0 0\n", i);

            printf("解析完了\n");

            break;
        }

        if (0 == elf->e_dyn.dyn64[i].d_tag % 2) {
            elf->e_dyn.dyn64[i].d_un.d_ptr = buffer_read8(buffer);

            printf("%d %X %x\n", i, elf->e_dyn.dyn64[i].d_tag, elf->e_dyn.dyn64[i].d_un.d_ptr);
        } else {
            elf->e_dyn.dyn64[i].d_un.d_val = buffer_read8(buffer);

            printf("%d %X %d\n", i, elf->e_dyn.dyn64[i].d_tag, elf->e_dyn.dyn64[i].d_un.d_val);
        }
    }
}

/**
 *  一个最复杂的ELF文件, 最多也就这些节, 重要的节我在前面打了*
 *
 *  *.interp 保持的是动态链接器的绝对路径
 *
 *  .note.ABI-tag
 *  .note.gnu.build-id 其中保存的是 GNU build ID，每一次构建都对应一个唯一 ID 号，主要是在 debug 时被用来唯一识别一个可执行文件
 *
 *  .gnu.hash 符号的 hash 表，使用 hash 表在字符串查找中可以明显提高性能
 *
 *  下面两个保存的是动态链接的符号相关信息
 *  *.dynsym
 *  *.dynstr
 *
 *  下面两个保存的是版本相关的内容信息
 *  .gnu.version
 *  .gnu.version_r
 *
 *  下面两个保存的是动态链接相关的重定位信息
 *  .rela.dyn
 *  .rela.plt
 *
 *  *.init   初始化代码的入口地址  _init
 *
 *  下面两个保存的是重定位时的全局偏移表
 *  .plt
 *  .plt.got
 *
 *  *.text   可执行文件的代码段
 *  *.fini   程序执行结束收尾代码的入口地址
 *  .rodata 保存了只读数据
 *
 *  下面两个保存的是异常处理相关的内容
 *  .eh_frame_hdr
 *  .eh_frame
 *
 *  .init_array
 *  .fini_array
 *
 *  .jcr 和java程序相关(???)
 *  *.dynamic 动态链接的相关信息
 *  .got 保存着全局偏移表
 *  .got.plt 和plt节一起提供了对导入的共享库函数的访问入口
 *  *.data 保存着初始化的全局变量等数据
 *  *.bss 保存的是未进行初始化的全局数据
 *  .comment 编译器的版本信息
 *  *.shstrtab 保存的是节头字符串表，该字符串保存了每个节的节名
 *  *.symtab 保存了符号表，与ELF符号和重定位有关
 *  *.strtab 保存了符号字符串表，表中的内容会被.symtab的ElfN_Sym结构中的st_name条目引用
 */
static void parse_elf64_section_item(buffer_t* buffer, elf_t* elf) {
    assert(NULL != buffer, "buffer = null");
    assert(NULL != elf, "elf = null");

    INFO_PRINT("开始解析节表的每个节\n");

    // index = 0的节只是占位,pass
    for (int i = 1; i < elf->e_elf.elf64->e_shnum; ++i) {
        Elf64_Shdr shdr = elf->e_shdr.shdr64[i];
        char* name = elf->e_shstring_table + shdr.sh_name;

        INFO_PRINT("开始解析节: %s\n", name);

        if (!strcmp(".shstrtab", name)) {
            printf("前面已经解析过,pass\n");
        } else if (!strcmp(".text", name)) {
            if (0 == shdr.sh_size) {
                printf("没有数据\n");
                exit(-1);
            }

            elf->e_code = Calloc(shdr.sh_size, 1);

            buffer->off = shdr.sh_offset;
            buffer_copy(buffer, elf->e_code, shdr.sh_size);

            printf("完成数据拷贝: off:%d(0x%x), size:%d\n", shdr.sh_offset, shdr.sh_offset, shdr.sh_size);
        } else if (!strcmp(".data", name)) {
            if (0 == shdr.sh_size) {
                printf("没有数据\n");
                continue;
            }

            printf(".data有数据未处理\n");
        } else if (!strcmp(".bss", name)) {
            if (0 == shdr.sh_size) {
                printf("没有数据\n");
                continue;
            }

            printf(".bss有数据未处理\n");
        } else if (!strcmp(".symtab", name)) {
            if (0 == shdr.sh_size) {
                printf("没有数据\n");
                continue;
            }

            parse_elf64_symtab(buffer, elf, &shdr);
        } else if (!strcmp(".strtab", name)) {
            if (0 == shdr.sh_size) {
                printf("没有数据\n");
                continue;
            }

            elf->e_string_table = Calloc(shdr.sh_size, 1);

            buffer->off = shdr.sh_offset;
            buffer_copy(buffer, elf->e_string_table, shdr.sh_size);

            printf("完成数据拷贝: off:%d(0x%x), size:%d\n", shdr.sh_offset, shdr.sh_offset, shdr.sh_size);
        } else if (!strcmp(".interp", name)) {  // 与动态链接有关, 动态链接器
            if (0 == shdr.sh_size) {
                printf("没有数据\n");
                continue;
            }

            elf->e_interp = Calloc(shdr.sh_size, 1);

            buffer->off = shdr.sh_offset;
            buffer_copy(buffer, elf->e_interp, shdr.sh_size);

            printf("完成数据拷贝: off:%d(0x%x), size:%d\n", shdr.sh_offset, shdr.sh_offset, shdr.sh_size);
        } else if (!strcmp(".dynamic", name)) {
            if (0 == shdr.sh_size) {
                printf("没有数据\n");
                continue;
            }

            parse_elf64_dynamic(buffer, elf, &shdr);
        } else {
            printf("不做解析过, pass\n");
        }
    }
}

static void parse_elf64_program_header_table(buffer_t* buffer, elf_t* elf) {
    INFO_PRINT("开始解析程序头\n");

    elf->e_phdr.phdr64 = Calloc(elf->e_elf.elf64->e_phnum, elf->e_elf.elf64->e_phentsize);

    buffer_reset(buffer);
    buffer->off += elf->e_elf.elf64->e_phoff;

    printf("index type vaddr\n");

    for (int i = 0; i < elf->e_elf.elf64->e_phnum; ++i) {
        elf->e_phdr.phdr64[i].p_type = buffer_read4(buffer);
        elf->e_phdr.phdr64[i].p_flags = buffer_read4(buffer);
        elf->e_phdr.phdr64[i].p_offset = buffer_read8(buffer);
        elf->e_phdr.phdr64[i].p_vaddr = buffer_read8(buffer);
        elf->e_phdr.phdr64[i].p_paddr = buffer_read8(buffer);
        elf->e_phdr.phdr64[i].p_filesz = buffer_read8(buffer);
        elf->e_phdr.phdr64[i].p_memsz = buffer_read8(buffer);
        elf->e_phdr.phdr64[i].p_align = buffer_read8(buffer);

        printf("%d 0x%x 0x%x\n", i, elf->e_phdr.phdr64[i].p_type, elf->e_phdr.phdr64[i].p_vaddr);
    }
}

void parse_elf64(buffer_t* buffer, elf_t* elf) {
    INFO_PRINT("开始解析64位ELF文件\n");

    Elf64_Ehdr* ehdr = Calloc(1, sizeof(Elf64_Ehdr));

    elf->e_elf.elf64 = ehdr;

    // 解析elf文件头
    parse_elf64_header(buffer, ehdr);

    // 解析.shstrtab(这里面存储的是节表的名称)
    parse_elf64_shstrtab(buffer, elf);

    // 解析节表
    parse_elf64_section_table(buffer, elf);

    // 解析节表中的每个节
    parse_elf64_section_item(buffer, elf);

    // 解析程序头
    if (0 != elf->e_elf.elf64->e_phnum) {
        parse_elf64_program_header_table(buffer, elf);
    }
}